package com.matrix.framework.core.upload;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.codec.multipart.FilePart;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Mono;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.channels.FileChannel;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 大文件分片上传服务
 *
 * Copyright © 海平面工作室 版权所有
 *
 * @Author: Leo
 * @Create: 2025-08-17 10:00:00
 * @Since 1.2
 */
@Service
public class FileUploadService {
    private static final Logger logger = LoggerFactory.getLogger(FileUploadService.class);
    
    @Value("${matrix.upload.temp-dir:uploads/temp}")
    private String tempDirectory;
    
    @Value("${matrix.upload.complete-dir:uploads/complete}")
    private String completeDirectory;
    
    // 跟踪每个文件的已上传分片
    private final Map<String, Map<Integer, Boolean>> uploadedChunks = new ConcurrentHashMap<>();

    /**
     * 处理文件分片上传
     * 
     * @param filePart 分片文件数据
     * @param chunkInfo 分片信息
     * @return 上传状态响应
     */
    public Mono<FileUploadResponse> uploadChunk(FilePart filePart, FileChunkDto chunkInfo) {
        return Mono.fromCallable(() -> {
            // 如果需要，创建目录
            createDirectoriesIfNeeded();
            
            // 获取此文件分片的目录
            String fileChunkDir = tempDirectory + File.separator + chunkInfo.getIdentifier();
            Path chunkDirPath = Paths.get(fileChunkDir);
            if (!Files.exists(chunkDirPath)) {
                Files.createDirectories(chunkDirPath);
            }
            
            // 保存分片文件的路径
            String chunkFilePath = fileChunkDir + File.separator + chunkInfo.getChunkNumber();
            Path chunkPath = Paths.get(chunkFilePath);
            
            return chunkPath;
        })
        .flatMap(chunkPath -> filePart.transferTo(chunkPath)
            .then(Mono.just(chunkPath)))
        .flatMap(chunkPath -> {
            // 跟踪此分片已上传
            trackChunkUpload(chunkInfo.getIdentifier(), chunkInfo.getChunkNumber());
            
            // 检查是否所有分片都已上传
            boolean allUploaded = areAllChunksUploaded(chunkInfo.getIdentifier(), chunkInfo.getTotalChunks());
            
            if (allUploaded) {
                // 如果所有分片都已上传，则合并分片
                return Mono.fromCallable(() -> mergeChunks(chunkInfo))
                    .map(mergedFilePath -> new FileUploadResponse(true, 
                        "所有分片上传并合并成功", mergedFilePath, true));
            } else {
                return Mono.just(new FileUploadResponse(true, 
                    "分片 " + chunkInfo.getChunkNumber() + " / " + chunkInfo.getTotalChunks() + " 上传成功", 
                    null, false));
            }
        })
        .onErrorResume(e -> {
            logger.error("上传分片时出错", e);
            return Mono.just(new FileUploadResponse(false, "上传分片时出错: " + e.getMessage()));
        });
    }
    
    /**
     * 将所有分片合并为完整文件
     * 
     * @param chunkInfo 文件及其分片的信息
     * @return 合并后的文件路径
     * @throws IOException 如果发生I/O错误
     */
    private String mergeChunks(FileChunkDto chunkInfo) throws IOException {
        // 如果输出目录不存在，则创建
        Path outputDir = Paths.get(completeDirectory);
        if (!Files.exists(outputDir)) {
            Files.createDirectories(outputDir);
        }
        
        // 定义输出文件路径
        String outputFilePath = completeDirectory + File.separator + chunkInfo.getFilename();
        Path outputPath = Paths.get(outputFilePath);
        
        // 创建输出文件
        try (FileChannel outputChannel = new FileOutputStream(outputPath.toFile(), true).getChannel()) {
            // 获取包含分片的目录
            String chunkDir = tempDirectory + File.separator + chunkInfo.getIdentifier();
            
            // 按顺序合并每个分片
            for (int i = 1; i <= chunkInfo.getTotalChunks(); i++) {
                Path chunkPath = Paths.get(chunkDir + File.separator + i);
                try (FileChannel inputChannel = new FileInputStream(chunkPath.toFile()).getChannel()) {
                    // 将内容从分片传输到输出文件
                    inputChannel.transferTo(0, inputChannel.size(), outputChannel);
                }
            }
        }
        
        // 清理临时分片
        cleanupChunks(chunkInfo.getIdentifier());
        
        return outputFilePath;
    }
    
    /**
     * 记录分片已上传
     * 
     * @param fileId 文件唯一标识符
     * @param chunkNumber 分片编号
     */
    private void trackChunkUpload(String fileId, int chunkNumber) {
        uploadedChunks.computeIfAbsent(fileId, k -> new HashMap<>())
                      .put(chunkNumber, true);
    }
    
    /**
     * 检查文件的所有分片是否已上传
     * 
     * @param fileId 文件唯一标识符
     * @param totalChunks 预期的分片总数
     * @return 如果所有分片都已上传则返回true，否则返回false
     */
    private boolean areAllChunksUploaded(String fileId, int totalChunks) {
        Map<Integer, Boolean> chunks = uploadedChunks.get(fileId);
        if (chunks == null) {
            return false;
        }
        
        for (int i = 1; i <= totalChunks; i++) {
            if (!chunks.getOrDefault(i, false)) {
                return false;
            }
        }
        
        return true;
    }
    
    /**
     * 成功合并后删除临时分片文件
     * 
     * @param fileId 文件唯一标识符
     */
    private void cleanupChunks(String fileId) {
        try {
            Path chunkDir = Paths.get(tempDirectory + File.separator + fileId);
            if (Files.exists(chunkDir)) {
                Files.walk(chunkDir)
                     .sorted((a, b) -> -a.compareTo(b)) // 反向排序，在删除目录之前删除文件
                     .forEach(path -> {
                         try {
                             Files.delete(path);
                         } catch (IOException e) {
                             logger.error("删除失败: " + path, e);
                         }
                     });
            }
            
            // 移除跟踪数据
            uploadedChunks.remove(fileId);
        } catch (IOException e) {
            logger.error("清理分片时出错", e);
        }
    }
    
    /**
     * 创建文件上传所需的目录
     * 
     * @throws IOException 如果创建目录时发生I/O错误
     */
    private void createDirectoriesIfNeeded() throws IOException {
        Path tempPath = Paths.get(tempDirectory);
        Path completePath = Paths.get(completeDirectory);
        
        if (!Files.exists(tempPath)) {
            Files.createDirectories(tempPath);
        }
        
        if (!Files.exists(completePath)) {
            Files.createDirectories(completePath);
        }
    }
    
    /**
     * 检查特定分片是否已上传
     * 
     * @param fileId 文件唯一标识符
     * @param chunkNumber 要检查的分片编号
     * @return 如果分片存在则返回Mono<Boolean>为true，否则为false
     */
    public Mono<Boolean> checkChunkExists(String fileId, int chunkNumber) {
        return Mono.fromCallable(() -> {
            // 首先检查内存中的跟踪
            Map<Integer, Boolean> chunks = uploadedChunks.get(fileId);
            if (chunks != null && chunks.getOrDefault(chunkNumber, false)) {
                return true;
            }
            
            // 检查磁盘
            String chunkPath = tempDirectory + File.separator + fileId + File.separator + chunkNumber;
            return Files.exists(Paths.get(chunkPath));
        });
    }
}
